package com.cloud.admin.support;

import com.cloud.admin.bean.UrlResource;
import com.cloud.admin.config.HealthCheckConfig;
import com.cloud.admin.influxdb.MetricEntity;
import com.cloud.admin.influxdb.MetricsRepository;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.net.URL;
import java.util.*;

@Slf4j
@Component
public class ServiceChecker {
    @Autowired
    private MetricsRepository metricsRepository;

    @Autowired
    private DiscoveryClient discoveryClient;

    @Autowired
    private HealthCheckConfig healthCheckConfig;

    @Autowired
    private RestTemplate restTemplate;

    private List<ServiceInstance> lastOnlineInstances;

    @Scheduled(fixedDelay = 15000)
    public void check() {
        if (healthCheckConfig.isCheck()) {
            log.info("service check begin...");
            checkInnerService();
            checkOuterService();
        }
    }

    private void checkInnerService() {
        // 查找所有在线服务实例
        List<ServiceInstance> onlineInstances = new ArrayList<>();
        List<String> services = discoveryClient.getServices();
        for (String serviceId : services) {
            if (healthCheckConfig.getIgnoreServices().contains(serviceId)) {
                continue;
            }
            List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
            if (CollectionUtils.isNotEmpty(instances)) {
                onlineInstances.addAll(instances);
            }
        }
        if (CollectionUtils.isEmpty(onlineInstances)) {
            return;
        }

        // 并行检测在线服务实例的响应速度
        List<MetricEntity> metrics = new ArrayList<>();
        ExecuteRunner.begin(onlineInstances.size());
        onlineInstances.forEach(instance -> ExecuteRunner.execute(() -> {
            try {
                URL url = new URL("http", instance.getHost(), instance.getPort(), "/actuator/health");
                metrics.add(checkHealth(instance.getServiceId(), url));
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }));
        ExecuteRunner.await();
        metricsRepository.saveAll(metrics);

        // 检查实例是否有下线的
        checkOnlineInstances(onlineInstances);
    }

    private void checkOnlineInstances(List<ServiceInstance> onlineInstances) {
        if (CollectionUtils.isNotEmpty(lastOnlineInstances)) {
            Map<String, ServiceInstance> onlineInstanceMap = new HashMap<>();
            for (ServiceInstance instance : onlineInstances) {
                onlineInstanceMap.put(getInstanceKey(instance), instance);
            }
            // 找出下线的实例
            Set<String> offlineInstances = new TreeSet<>();
            for (ServiceInstance instance : lastOnlineInstances) {
                String instanceKey = getInstanceKey(instance);
                if (!onlineInstanceMap.containsKey(instanceKey)) {
                    String serviceInstance = "serviceName=" + instance.getServiceId() + " ";
                    serviceInstance += "hostPort=" + instanceKey + " ";
                    offlineInstances.add(serviceInstance);
                }
            }
            // 错误信息组装
            if (CollectionUtils.isNotEmpty(offlineInstances)) {
                StringBuilder msgBuilder = new StringBuilder("离线的服务实例 >>> ");
                for (String s : offlineInstances) {
                    msgBuilder.append(s);
                }
                log.error(msgBuilder.toString());
            }
        }
        lastOnlineInstances = onlineInstances;
    }

    private String getInstanceKey(ServiceInstance instance) {
        return instance.getHost() + ":" + instance.getPort();
    }

    private void checkOuterService() {
        List<UrlResource> urlResourceList = healthCheckConfig.getCheckUrls();
        if (CollectionUtils.isNotEmpty(urlResourceList)) {
            // 并行检测在线服务实例的响应速度
            List<MetricEntity> metrics = new ArrayList<>();
            ExecuteRunner.begin(urlResourceList.size());
            urlResourceList.forEach(urlResource -> ExecuteRunner.execute(() -> {
                try {
                    URL url = new URL(urlResource.getUrl());
                    metrics.add(checkHealth(urlResource.getName(), url));
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                }
            }));
            ExecuteRunner.await();
            metricsRepository.saveAll(metrics);
        }
    }

    private MetricEntity checkHealth(String serviceName, URL url) {
        String requestURL = url.toString();
        long beginTimeMillis = System.currentTimeMillis();
        boolean successful = false;
        try {
            ResponseEntity<String> responseEntity = restTemplate.getForEntity(requestURL, String.class);
            if (responseEntity.getStatusCode() == HttpStatus.OK) {
                successful = true;
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        long endTimeMillis = System.currentTimeMillis();
        long costTime = endTimeMillis - beginTimeMillis;
        String message = "requestURL={} costTime={}";
        if (costTime >= 300) {
            log.warn(message, requestURL, costTime);
        } else {
            log.info(message, requestURL, costTime);
        }

        MetricEntity metricEntity = new MetricEntity();
        metricEntity.setApp(serviceName);
        metricEntity.setInstance(url.getHost() + ":" + url.getPort());
        metricEntity.setResource("/" + serviceName + url.getPath());
        metricEntity.setResourceCode(metricEntity.getResource().hashCode());
        metricEntity.setTimestamp(TimeSequence.getTimeMillis());
        metricEntity.setGmtCreate(metricEntity.getTimestamp());
        metricEntity.setGmtModified(metricEntity.getTimestamp());
        metricEntity.setCount(1);
        metricEntity.setPassQps(1L);
        metricEntity.setBlockQps(0L);
        metricEntity.setRt(costTime);
        if (successful) {
            metricEntity.setSuccessQps(1L);
            metricEntity.setExceptionQps(0L);
        } else {
            metricEntity.setSuccessQps(0L);
            metricEntity.setExceptionQps(1L);
        }
        return metricEntity;
    }
}
